﻿using FantasticMusicPlayer.dbo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.OS.Storage;
using Android.Util;
using System.Globalization;
using NightPlayer;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using static Android.Provider.MediaStore.Audio;

namespace FantasticMusicPlayer
{
    class CurrentDirectorySongProvider : IPlayListProvider
    {
        String savesdir = "point.conf";
        private List<PlayList> unfilteredPlaylist;
        private Dictionary<string, AudioLoudnessEntry> loudnessEntries;

        

        private HashSet<string> musicRelativePaths = new HashSet<string>();
        public bool MusicExists(string relativePath)
        {
            return musicRelativePaths.Contains(relativePath);
        }

        public CurrentDirectorySongProvider(String musicRoot,String savesdir) {
            
            this.savesdir = savesdir;
            _musicRoot = musicRoot;
            String dir = MusicRoot;
            Program.OnErrorClearCache += Program_OnErrorClearCache;
            Log.Info("MusicInit","Scanning Dir "+dir);
            if (!AndroidIO.Directory.Exists(dir))
            {
                AndroidIO.Directory.CreateDirectory(dir);
            }
            Stopwatch sw = Stopwatch.StartNew();
            if (AndroidIO.GetImpl() is TheFxxkingSAFFileIOImpl fsImpl)
            {
                long lastModifiedTime = -1;
                if (!Program.AppSettings.PreferLibraryCache)
                {
                    lastModifiedTime = fsImpl.BuildCache();
                }

                if (IsCacheValid(lastModifiedTime)) {
                    Console.WriteLine("Brute FSCache take " + sw.ElapsedMilliseconds + " ms");
                    sw.Restart();
                    loudnessEntries = ReadLoudnessConfigFile();
                    LoadCache();
                    Console.WriteLine("Load Cache take " + sw.ElapsedMilliseconds + " ms");
                    sw.Stop();
                    
                    return;
                }
            }


            if (!AndroidIO.File.Exists(AndroidIO.Path.Combine(MusicRoot,"收藏.pl"))) { AndroidIO.File.Create(AndroidIO.Path.Combine(MusicRoot, "收藏.pl")); }
            Console.WriteLine("Init data take "+sw.ElapsedMilliseconds+" ms");
            sw.Restart();
            loudnessEntries = ReadLoudnessConfigFile(); 
            Console.WriteLine("Read loudness take " + sw.ElapsedMilliseconds + " ms");
            sw.Restart();
            EnumrateDir(dir);
            Console.WriteLine("Enumerate Dir take " + sw.ElapsedMilliseconds + " ms");
            sw.Restart();
            unfilteredPlaylist = _playlists;
            unfilteredPlaylist.Where(f => f is VirtualDir).Cast<VirtualDir>().ToList().ForEach(f => f.checkUpdate());
            _playlists = unfilteredPlaylist.Where(p => p.Songs.Count > 0).ToList();

            Console.WriteLine("Load playlist take " + sw.ElapsedMilliseconds + " ms");
            sw.Stop();
            if (AndroidIO.GetImpl() is TheFxxkingSAFFileIOImpl)
            {

                SaveCache();
            }

        }

        private void Program_OnErrorClearCache(object? sender, EventArgs e)
        {
            File.Delete(CacheFilePath);
            File.Delete(CacheFileTimePath);
        }

        const string configFileName = ".loudness.cfg";
        public Dictionary<string, AudioLoudnessEntry> ReadLoudnessConfigFile()
        {
            var entries = new Dictionary<string, AudioLoudnessEntry>();
            var configFilePath = AndroidIO.Path.Combine(MusicRoot, configFileName);


            if (!AndroidIO.File.Exists(configFilePath))
            {
                return new Dictionary<string, AudioLoudnessEntry>();
            }

            foreach (var line in AndroidIO.File.ReadAllLines(configFilePath))
            {
                if (string.IsNullOrWhiteSpace(line))
                {
                    continue; // Skip empty lines
                }

                var parts = line.Split('|');
                if (parts.Length != 4)
                {
                    Console.WriteLine("Invalid line format in loudness configuration file.");
                    continue;
                }
                try
                {
                    var entry = new AudioLoudnessEntry
                    {
                        RelativePath = parts[0],
                        PeakDB = float.Parse(parts[1], CultureInfo.InvariantCulture),
                        LUFS = float.Parse(parts[2], CultureInfo.InvariantCulture),
                    };
                    entries.Add(entry.RelativePath, entry);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error while read line " + line);
                    Console.WriteLine(ex.ToString());
                }

            }

            return entries;
        }

        private string CacheFilePath
        {
            get
            {
                return System.IO.Path.Combine(System.IO.Path.GetTempPath(), "cachedsaf_v0.tmp");
            }
        }
        private string CacheFileTimePath
        {
            get
            {
                return CacheFilePath+".time";
            }
        }
        private void SaveCache()
        {
            using (var fs = File.Create(CacheFilePath))
            {
                BinaryWriter bw = new BinaryWriter(fs);
                bw.Write(PlayLists.Count);
                foreach (var playList in PlayLists)
                {
                    var dirType = playList is VirtualDir ? 1 : 0;
                    bw.Write(dirType);
                    bw.Write(playList.Name);
                    if (dirType == 1) { 
                        bw.Write(((VirtualDir)playList).Path);
                    }
                    var songs = playList.Songs;
                    bw.Write(songs.Count);
                    foreach (var song in songs)
                    {
                        bw.Write(song.Path);
                        bw.Write(song.Name);
                        if(song.Loudness == null)
                        {
                            bw.Write(0f);
                            bw.Write(0f);
                        }
                        else
                        {
                            bw.Write(song.Loudness.LUFS);
                            bw.Write(song.Loudness.PeakDB);
                        }
                    }
                }
            }
        }
        private void LoadCache()
        {
            using (var fileStream = File.OpenRead(CacheFilePath)) {
                _playlists = new List<PlayList>();
                BinaryReader br = new BinaryReader(fileStream);
                int dirCount = br.ReadInt32();
                for (int dirCurrent = 0; dirCurrent < dirCount; dirCurrent++)
                {
                    int dirType = br.ReadInt32();//0 = directory,1 = virtual play list
                    string dirName = br.ReadString();
                    string dirPath = "";
                    if (dirType == 1)
                    {
                        dirPath = br.ReadString();
                    }
                    int songCount = br.ReadInt32();
                    List<SongEntry> entries = new List<SongEntry>();
                    for (int songCurrent = 0; songCurrent < songCount; songCurrent++) { 
                        string path = br.ReadString();
                        string name = br.ReadString();
                        float loudnessLUFS = br.ReadSingle();
                        float loudnessPeak = br.ReadSingle();
                        SongEntry se = new SongEntry();
                        se.Path = path;
                        se.Name = name;
                        se.Loudness = new SongEntry.LoudnessInfo() { LUFS = loudnessLUFS,PeakDB = loudnessPeak };
                        entries.Add(se);
                    }
                    if(dirType == 0)
                    {
                        PlayList pl = new PlayList(dirName,entries);
                        _playlists.Add(pl);
                    }
                    if(dirType == 1)
                    {
                        VirtualDir vd = new VirtualDir(this, dirName, dirPath,MusicRoot, this.loudnessEntries);
                        vd.loadCache(entries);
                        _playlists.Add(vd);
                    }
                }
            }
        }
        private bool IsCacheValid(long modifiedTime)
        {
            if ((!File.Exists(CacheFilePath)))
            {
                return false;
            }
            if (Program.AppSettings.PreferLibraryCache)
            {
                return true;
            }
            var fileTime = new Java.IO.File(CacheFilePath);
            if(fileTime.LastModified() >= modifiedTime)
            {
                return true;
            }
            return false;
        }

        public class AudioLoudnessEntry
        {
            public string RelativePath;
            public float PeakDB;
            public float LUFS;
            public DateTime FileModified;
        }
        List<String> availableExtenstions = new List<string>(new string[] {".mp3",".wav",".flac",".ape",".aac" });

        public void EnumrateDir(String root) {
            if (root.Contains("/.") || root.Contains("\\."))
            {
                return;
            }
            PlayList pl = new PlayList(AndroidIO.Path.GetFileName(root));
            PlayLists.Add(pl);
            var fileList = AndroidIO.Directory.EnumerateFiles(root).OrderBy(o => o);
            foreach (var item in fileList)
            {
                if (item.ToLower().EndsWith(".pl"))
                {
                    SetupVirtualList(item);
                }
                if(availableExtenstions.Any(a => item.ToLower().EndsWith(a)))
                {
                    pl.Songs.Add(createSongEntry(item));
                }
            }

            AndroidIO.Directory.EnumerateDirectories(root).OrderBy(o => o).ToList().ForEach(o => EnumrateDir(o));
        }

        private SongEntry createSongEntry(string f)
        {
            var se = new SongEntry(f);
            var rp = se.getRelativePath();
            musicRelativePaths.Add(rp);
            if (loudnessEntries.ContainsKey(rp))
            {
                var entry = loudnessEntries[rp];
                se.Loudness = new SongEntry.LoudnessInfo { LUFS = entry.LUFS, PeakDB = entry.PeakDB };
            }
            return se;
        }
        public void UpdatePlaylist()
        {
            _playlists = unfilteredPlaylist.Where(p => p.Songs.Count > 0).ToList();
        }

        public void SetupVirtualList(String virtualList)
        {
            string name = AndroidIO.Path.GetFileNameWithoutExtension(virtualList);
            PlayLists.Add(new VirtualDir(this, name, virtualList, MusicRoot, loudnessEntries));
        }

        private List<PlayList> _playlists = new List<PlayList>();
        public List<PlayList> PlayLists => _playlists;

        public PlayList LastPlayList { get; set ; }
        public SongEntry LastSong { get ; set ; }

        public void loadProgress()
        {
            int folderpos = 0;
            int songpos = 0;
            if (AndroidIO.File.Exists(SavePath)) {
                try
                {
                    String[] content = AndroidIO.File.ReadAllLines(SavePath);
                    folderpos = int.Parse(content[0]);
                    songpos = int.Parse(content[1]);
                }
                catch { }
            }

            LastPlayList = PlayLists[Math.Min(folderpos,PlayLists.Count-1)];
            LastSong = LastPlayList.Songs[Math.Min(songpos, LastPlayList.Songs.Count - 1)];
        }


        public String SavePath
        {

            get {
                return savesdir;
            }
        }

        private static string _musicRoot = "MP3";

        public static String MusicRoot
        {
            get
            {
                return AndroidIO.Path.GetFullPath(_musicRoot);
            }
        }
        public void saveProgress()
        {

            AndroidIO.File.WriteAllLines(SavePath, new string[] {PlayLists.IndexOf(LastPlayList)+"",LastPlayList.Songs.IndexOf(LastSong)+"" });
        }

        internal VirtualDir GetFavList()
        {
            var favListPath = AndroidIO.Path.Combine(MusicRoot, "收藏.pl");
            var existingCache = PlayLists.Where(v => v is VirtualDir).Cast<VirtualDir>().Where(d => d.Path == favListPath).FirstOrDefault();
            return existingCache ?? new VirtualDir(this, "收藏", AndroidIO.Path.Combine(MusicRoot, "收藏.pl"), MusicRoot,loudnessEntries);
        }
    }

    class VirtualDir : PlayList
    {
        private string fileName;
        private DateTime lastmodified = DateTime.MinValue;
        private string rootdir;
        private List<SongEntry> songs = new List<SongEntry>();
        private Dictionary<string, CurrentDirectorySongProvider.AudioLoudnessEntry> loudnessEntries;
        CurrentDirectorySongProvider provider;
        public VirtualDir(CurrentDirectorySongProvider provider, string name, string file, string root, Dictionary<string, CurrentDirectorySongProvider.AudioLoudnessEntry> loudnessData = null) : base(name)
        {
            this.provider = provider;
            fileName = file;
            rootdir = root;
            this.loudnessEntries = loudnessData;
        }

        public void loadCache(IEnumerable<SongEntry> entries)
        {
            lastmodified = DateTime.Now;
            songs = new List<SongEntry>();
            songs.AddRange(entries);
        }


        public void checkUpdate()
        {
            if (AndroidIO.File.GetLastWriteTime(fileName) > lastmodified)
            {
                songs.Clear();
                songs.AddRange(AndroidIO.File.ReadAllLines(fileName).Where(f => !string.IsNullOrWhiteSpace(f) && provider.MusicExists(f)).Select(f => createSongEntry(AndroidIO.Path.Combine(rootdir, f.Replace('/', AndroidIO.Path.DirectorySeparatorChar)))));
                Console.WriteLine("Loaded playlist " + Name + " with " + songs.Count + " songs.");
                lastmodified = AndroidIO.File.GetLastWriteTime(fileName);
            }
        }
        private SongEntry createSongEntry(string f)
        {
            var se = new SongEntry(f);
            var rp = se.getRelativePath();
            if (loudnessEntries != null)
            {
                if (loudnessEntries.ContainsKey(rp))
                {
                    var entry = loudnessEntries[rp];
                    se.Loudness = new SongEntry.LoudnessInfo { LUFS = entry.LUFS, PeakDB = entry.PeakDB };
                }
            }

            return se;
        }

        public void save()
        {
            AndroidIO.File.WriteAllLines(fileName, songs.Select(s => s.getRelativePath()));
        }

        public override List<SongEntry> Songs { get { checkUpdate(); return songs; } set { } }

        public string Path { get { return fileName; } }
    }
}
